package com.zsx.shopping.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.zsx.shopping.common.api.search.dto.SkuEsDto;
import com.zsx.shopping.common.exception.ShoppingException;
import com.zsx.shopping.search.config.ElasticSearchConfig;
import com.zsx.shopping.search.constant.SearchConstant;
import com.zsx.shopping.search.param.SearchParam;
import com.zsx.shopping.search.service.SearchService;
import com.zsx.shopping.search.vo.SearchResultVo;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.Max;
import org.elasticsearch.search.aggregations.metrics.MaxAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.Min;
import org.elasticsearch.search.aggregations.metrics.MinAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Resource
    private RestHighLevelClient restClient;

    @Override
    public SearchResultVo searchProduct(SearchParam searchParam) {
        Integer pageNum = searchParam.getPageNum();
        if (pageNum == null || pageNum < 1) {
            searchParam.setPageNum(1);
        }
        String sort = searchParam.getSort();
        if (StringUtils.isEmpty(sort)) {
            searchParam.setSort(SearchConstant.DEFAULT_SORT);
        }
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        SearchResponse searchResponse;
        try {
            searchResponse = restClient.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            throw new ShoppingException("搜索引擎搜索商品数据失败");
        }
        return buildSearchResult(searchResponse, searchParam);
    }

    private SearchRequest buildSearchRequest(SearchParam searchParam) {

        // 布尔查询

        // 商品名称、分类、品牌
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuName", searchParam.getKeyword()));
        }
        if (searchParam.getCategory3id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId", searchParam.getCategory3id()));
        }
        if (searchParam.getBrandId() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brandId", searchParam.getBrandId()));
        }

        // 属性
        if (!CollectionUtils.isEmpty(searchParam.getAttrs())) {
            searchParam.getAttrs().forEach(item -> {
                if (!StringUtils.isEmpty(item)) {
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

                    String[] s = item.split("_");
                    String attrId = s[0];
                    String attrValue = s[1];
                    boolQuery.must(QueryBuilders.termQuery("attrs.id", attrId));
                    boolQuery.must(QueryBuilders.termQuery("attrs.value", attrValue));

                    NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", boolQuery, ScoreMode.None);
                    boolQueryBuilder.filter(nestedQueryBuilder);
                }
            });
        }

        // 库存
        if (searchParam.getHasStock() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock()));
        }

        // 价格
        if (!StringUtils.isEmpty(searchParam.getSkuPrice())) {
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
            String[] price = searchParam.getSkuPrice().split("_");
            if (price.length >= 2) {
                rangeQueryBuilder.gte(price[0]).lte(price[1]);
            } else {
                if (searchParam.getSkuPrice().startsWith("_")) {
                    rangeQueryBuilder.lte(price[0]);
                }
                if (searchParam.getSkuPrice().endsWith("_")) {
                    rangeQueryBuilder.gte(price[0]);
                }
            }
            boolQueryBuilder.filter(rangeQueryBuilder);
        }

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(boolQueryBuilder);

        // 排序
        if (!StringUtils.isEmpty(searchParam.getSort())) {
            String sort = searchParam.getSort();
            String[] sortFields = sort.split("_");
            SortOrder sortOrder = "asc".equalsIgnoreCase(sortFields[1]) ? SortOrder.ASC : SortOrder.DESC;
            searchSourceBuilder.sort(sortFields[0], sortOrder);
        }

        // 分页
        searchSourceBuilder.from((searchParam.getPageNum() - 1) * SearchConstant.DEFAULT_PAGE_SIZE);
        searchSourceBuilder.size(SearchConstant.DEFAULT_PAGE_SIZE);

        // 高亮
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuName");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        // 聚合分析

        // 品牌
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId").size(50);
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg")
                .field("brandName").size(1));
        brand_agg.subAggregation(AggregationBuilders.terms("brand_image_agg")
                .field("brandImage").size(1));
        searchSourceBuilder.aggregation(brand_agg);

        // 属性
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.id");
        attr_agg.subAggregation(attr_id_agg);
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.name").size(1));
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.value").size(50));
        searchSourceBuilder.aggregation(attr_agg);

        // 价格
        MinAggregationBuilder min_skuPrice_agg = AggregationBuilders.min("min_skuPrice_agg").field("skuPrice");
        MaxAggregationBuilder max_skuPrice_agg = AggregationBuilders.max("max_skuPrice_agg").field("skuPrice");
        searchSourceBuilder.aggregation(min_skuPrice_agg);
        searchSourceBuilder.aggregation(max_skuPrice_agg);

        return new SearchRequest(new String[]{SearchConstant.PRODUCT_INDEX}, searchSourceBuilder);
    }

    private SearchResultVo buildSearchResult(SearchResponse response, SearchParam searchParam) {
        SearchResultVo searchResultVo = new SearchResultVo();
        SearchHits searchHits = response.getHits();

        // 总记录数,总页数,当前页码
        int total = (int) searchHits.getTotalHits().value;
        int pageSize = SearchConstant.DEFAULT_PAGE_SIZE;
        int maxPageNum = total % pageSize == 0 ? (total == 0 ? 1 : total / pageSize) : total / pageSize + 1;
        searchResultVo.setTotal(total);
        searchResultVo.setMaxPageNum(maxPageNum);

        // 商品数据
        SearchHit[] hits = searchHits.getHits();
        if (hits != null) {
            List<SearchResultVo.Sku> srvSkus = new ArrayList<>();
            for (SearchHit hit : hits) {
                SkuEsDto skuEsDTO = JSON.parseObject(hit.getSourceAsString(), SkuEsDto.class);
                // 判断是否按关键字检索,若是则显示高亮,否则不显示
                if (!StringUtils.isEmpty(searchParam.getKeyword())) {
                    HighlightField highlightField = hit.getHighlightFields().get("skuName");
                    String skuTitle = highlightField.getFragments()[0].string();
                    skuEsDTO.setSkuName(skuTitle);
                }
                SearchResultVo.Sku srvSku = new SearchResultVo.Sku();
                srvSku.setId(skuEsDTO.getSkuId());
                srvSku.setName(skuEsDTO.getSkuName());
                srvSku.setPrice(skuEsDTO.getSkuPrice());
                srvSku.setDefaultImage(skuEsDTO.getSkuDefaultImage());
                srvSkus.add(srvSku);
            }
            searchResultVo.setSkus(srvSkus);
        }

        // 属性
        List<SearchResultVo.Attr> attrVos = new ArrayList<>();
        ParsedNested attrsAgg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResultVo.Attr attrVo = new SearchResultVo.Attr();

            long attrId = bucket.getKeyAsNumber().longValue();
            attrVo.setId(attrId);

            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrVo.setName(attrName);

            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
            List<String> attrValues = attrValueAgg.getBuckets().stream()
                    .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                    .collect(Collectors.toList());
            attrVo.setValues(attrValues);
            attrVos.add(attrVo);
        }
        searchResultVo.setAttrs(attrVos);

        // 品牌
        List<SearchResultVo.Brand> brandVos = new ArrayList<>();
        ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");
        for (Terms.Bucket bucket : brandAgg.getBuckets()) {
            SearchResultVo.Brand brandVo = new SearchResultVo.Brand();

            long brandId = bucket.getKeyAsNumber().longValue();
            brandVo.setId(brandId);

            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brandVo.setName(brandName);

            ParsedStringTerms brandImageAgg = bucket.getAggregations().get("brand_image_agg");
            String brandImage = brandImageAgg.getBuckets().get(0).getKeyAsString();
            brandVo.setImage(brandImage);

            brandVos.add(brandVo);
        }
        searchResultVo.setBrands(brandVos);

        // 价格区间
        double minSkuPrice = ((Min) response.getAggregations().get("min_skuPrice_agg")).getValue();
        double maxSkuPrice = ((Max) response.getAggregations().get("max_skuPrice_agg")).getValue();
        int begin = ((int) minSkuPrice) / 500;
        int end = ((int) maxSkuPrice) / 500;
        ArrayList<SearchResultVo.PriceRange> priceRanges = new ArrayList<>();
        for (int i = begin; i <= end; i++) {
            SearchResultVo.PriceRange priceRange = new SearchResultVo.PriceRange();
            priceRange.setMin(i * 500);
            priceRange.setMax((i + 1) * 500 - 1);
            priceRanges.add(priceRange);
        }
        searchResultVo.setPriceRanges(priceRanges);

        return searchResultVo;
    }
}
